home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / TSR / TSRSRC35 / RELNET.PAS < prev    next >
Pascal/Delphi Source File  |  1993-10-21  |  48KB  |  1,490 lines

  1. {**************************************************************************
  2. *   RELNET - releases memory above the last MARKNET call made.            *
  3. *   Copyright (c) 1986,1993 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. ***************************************************************************
  6. *   Version 2.7 3/4/89                                                    *
  7. *     first public release                                                *
  8. *     (based on RELEASE 2.6)                                              *
  9. *   Version 2.8 3/10/89                                                   *
  10. *     restore the DOS environment                                         *
  11. *     restore the async ports                                             *
  12. *   Version 2.9 5/4/89                                                    *
  13. *     ignore file marks                                                   *
  14. *   Version 3.0 9/25/91                                                   *
  15. *     make compatible with DOS 5                                          *
  16. *     handle NetWare IPX better, allowing release of NETBIOS TSR          *
  17. *     add Quiet option                                                    *
  18. *     update for new WATCH behavior                                       *
  19. *     restore BIOS LPT port data areas                                    *
  20. *     restore XMS allocation                                              *
  21. *     add code for tracking high memory                                   *
  22. *   Version 3.1 11/4/91                                                   *
  23. *     restore less of DOS variables table (more deactivates high memory   *
  24. *       after a release)                                                  *
  25. *     add option to disable IPX socket shutdown                           *
  26. *   Version 3.2 11/22/91                                                  *
  27. *     version 3.1 crashed under DOS 3.3 (RestoreDosTable)                 *
  28. *     change method of accessing high memory                              *
  29. *     reverse order in which memory blocks are released to work           *
  30. *       correctly with the 386MAX high memory manager                     *
  31. *     merge blocks in high memory after release (QEMM doesn't)            *
  32. *   Version 3.3 1/8/92                                                    *
  33. *     add /H to use high memory optionally                                *
  34. *     new features for parsing and getting command line options           *
  35. *   Version 3.4 2/14/92                                                   *
  36. *     release HMA when appropriate                                        *
  37. *     fix hang that occurs when QEMM LOADHI didn't have space to          *
  38. *       load a mark high                                                  *
  39. *   Version 3.5                                                           *
  40. *     modify RestoreEMSMap to deal with EMS blocks for which a mapping    *
  41. *       context has been stored                                           *
  42. *     accept DOS 6                                                        *
  43. *     solve problem with RELNET /U for a MARK loaded high with QEMM 7.0   *
  44. *     solve problem with RELNET /U for a MARK loaded high with 386MAX     *
  45. *     restore BIOS com port addresses at $40:$0                           *
  46. ***************************************************************************
  47. *   Telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  48. *   Requires Turbo Pascal 6 or 7 to compile.                              *
  49. ***************************************************************************}
  50.  
  51. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  52. {$M 16384,0,655360}
  53. {.$DEFINE Debug}
  54.  
  55. program RelNet;
  56.  
  57. uses
  58.   Dos,
  59.   MemU,
  60.   Ipx,
  61.   Xms,
  62.   Ems;
  63.  
  64. const
  65.   MarkFOpen : Boolean = False;       {True while mark file is open}
  66.   VectorsRestored : Boolean = False; {True after old vector table restored}
  67.  
  68. var
  69.   Blocks : BlockArray;
  70.   markBlock : BlockType;
  71.   BlockMax : BlockType;
  72.   markPsp : Word;
  73.  
  74.   MarkName : PathStr;
  75.  
  76.   ReturnCode : Word;
  77.   StartMCB : Word;
  78.   HiMemSeg : Word;
  79.  
  80.   Revector8259 : Boolean;
  81.   DealWithIpx : Boolean;
  82.   DealWithEMS : Boolean;
  83.   DealWithXMS : Boolean;
  84.   KeepMark : Boolean;
  85.   RestoreEnvir : Boolean;
  86.   ResetTimer : Boolean;
  87.   RestoreComm : Boolean;
  88.   MemMark : Boolean;
  89.   FilMark : Boolean;
  90.   Verbose : Boolean;
  91.   Quiet : Boolean;
  92.   OptUseHiMem : Boolean;
  93.   UseHiMem : Boolean;
  94.   DealWithCDs : Boolean;
  95.  
  96.   Keys : string[16];
  97.  
  98.   MarkEHandles : Word;
  99.   CurrEHandles : Word;
  100.   MarkEmsHandles : PageArrayPtr;
  101.   CurrEmsHandles : PageArrayPtr;
  102.  
  103.   TrappedBytes : LongInt;
  104.  
  105.   MarkXHandles : Word;
  106.   CurrXHandles : Word;
  107.   MarkXmsHandles : XmsHandlesPtr;
  108.   CurrXmsHandles : XmsHandlesPtr;
  109.   MarkHmaStatus : Byte;
  110.   CurHmaStatus : Byte;
  111.  
  112.   {Save areas read in from file mark}
  113.   Vectors : array[0..1023] of Byte;
  114.   EGAsavTable : array[0..7] of Byte;
  115.   IntComTable : array[0..15] of Byte;
  116.   ParentSeg : Word;
  117.   ParentLen : Word;
  118.   BiosLowTable : array[0..17] of Byte;
  119.   DevA : DeviceArray;             {Temporary array of device headers}
  120.   DevCnt : Word;                  {Number of device headers}
  121.   CommandPsp : array[1..$100] of Byte; {Buffer for COMMAND.COM PSP}
  122.   DosData : array[1..$200] of Byte; {Buffer for DOS data area}
  123.   DosTableSize : Word;
  124.   DosTable : Pointer;             {Dos internal variables}
  125.   FileTableA : array[1..5] of SftRecPtr; {Points to system file table buffers}
  126.   FileTableCnt : Word;            {Number of system file table blocks}
  127.   FileRecSize : Word;             {Bytes in internal DOS file record}
  128.   CurDirRecSize : Word;           {Bytes in internal DOS curdir record}
  129.   PatchOfst : Word;               {Address of COMMAND.COM patch}
  130.   PatchSegm : Word;
  131.   EnvLen : Word;                  {Bytes in DOS environment}
  132.   EnvPtr : Pointer;               {Pointer to copy of DOS environment}
  133.   PicMask : Byte;                 {8259 interrupt mask}
  134.   ComData : ComArray;             {Communications data array}
  135.   McbG : McbGroup;                {Allocated Mcbs}
  136.  
  137.   TestPtr : DeviceHeaderPtr;      {Test pointer while getting started on chain}
  138.   DevicePtr : DeviceHeaderPtr;    {Pointer to the next device header}
  139.   DeviceSegment : Word;           {Current device segment}
  140.   DeviceOffset : Word;            {Current device offset}
  141.   MarkF : file;                   {Saved system information file}
  142.   DosPtr : ^DosRec;               {Pointer to internal DOS variable table}
  143.   CommandSeg : Word;              {Segment of low memory COMMAND.COM}
  144.   TmpCommandSeg : Word;           {Segment of COMMAND.COM returned by FindTheBlocks}
  145.  
  146.   CDCnt : Word;                   {For tracking MSCDEX information}
  147.   CDInfo : CDROMDeviceArray;
  148.  
  149.   procedure NoRestoreHalt(ReturnCode : Word);
  150.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  151.   begin
  152.     if VectorsRestored then begin
  153.       Close(Output);
  154.       asm
  155.         mov ah,$4C
  156.         mov al,byte(ReturnCode)
  157.         int $21
  158.       end;
  159.     end else
  160.       System.Halt(ReturnCode);
  161.   end;
  162.  
  163.   procedure RemoveMarkFile;
  164.     {-Close and remove the mark file}
  165.   begin
  166.     Close(MarkF);
  167.     if IoResult = 0 then
  168.       if not KeepMark then begin
  169.         Erase(MarkF);
  170.         if IoResult = 0 then ;
  171.       end;
  172.     MarkFOpen := False;
  173.   end;
  174.  
  175.   procedure Abort(Msg : String);
  176.     {-Halt in case of error}
  177.   begin
  178.     if MarkFOpen then
  179.       RemoveMarkFile;
  180.     WriteLn(Msg);
  181.     Halt(255);
  182.   end;
  183.  
  184.   function FindMark(MarkName, MarkID : String;
  185.                     MarkOffset : Word;
  186.                     var MemMark, FilMark : Boolean;
  187.                     var B : BlockType) : Boolean;
  188.     {-Find the last memory block matching idstring at offset idoffset}
  189.   var
  190.     BPsp : Word;
  191.  
  192.     function HasIDstring(Segment : Word;
  193.                          IdString : String;
  194.                          IdOffset : Word) : Boolean;
  195.       {-Return true if idstring is found at segment:idoffset}
  196.     var
  197.       Tstring : String;
  198.       Len : Byte;
  199.     begin
  200.       Len := Length(IdString);
  201.       Tstring[0] := Chr(Len);
  202.       Move(Mem[Segment:IdOffset], Tstring[1], Len);
  203.       HasIDstring := (Tstring = IdString);
  204.     end;
  205.  
  206.     function GetMarkName(Segment : Word) : String;
  207.       {-Return a cleaned up mark name from the segment's PSP}
  208.     var
  209.       Tstring : String;
  210.       Tlen : Byte absolute Tstring;
  211.     begin
  212.       Move(Mem[Segment:$80], Tstring[0], 128);
  213.       while (Tlen > 0) and ((Tstring[1] = ' ') or (Tstring[1] = ^I)) do
  214.         Delete(Tstring, 1, 1);
  215.       while (Tlen > 0) and ((Tstring[Tlen] = ' ') or (Tstring[Tlen] = ^I)) do
  216.         Dec(Tlen);
  217.       GetMarkName := StUpcase(Tstring);
  218.     end;
  219.  
  220.     function MatchMemMark(Segment : Word;
  221.                           MarkName : String;
  222.                           var B : BlockType) : Boolean;
  223.       {-Return true if MemMark is unnamed or matches current name}
  224.     var
  225.       FoundIt : Boolean;
  226.       Tstring : String;
  227.     begin
  228.       {Check the mark name stored in the PSP of the mark block}
  229.       Tstring := GetMarkName(Segment);
  230.       FoundIt := (Tstring = MarkName);
  231.       if not FoundIt then begin
  232.         if (Tstring <> '') and (Tstring[1] = ProtectChar) then
  233.           {Current mark is protected, stop searching}
  234.           B := 1;
  235.         Dec(B);
  236.       end;
  237.       MatchMemMark := FoundIt;
  238.     end;
  239.  
  240.     function MatchFilMark(Segment : Word;
  241.                           MarkName : String;
  242.                           var B : BlockType) : Boolean;
  243.       {-Return true if FilMark is unnamed or matches current name}
  244.     var
  245.       FoundIt : Boolean;
  246.     begin
  247.       {Check the mark name stored in the PSP of the mark block}
  248.       FoundIt := (GetMarkName(Segment) = MarkName);
  249.       if FoundIt then begin
  250.         {Assure named file exists}
  251.         if Verbose then
  252.           WriteLn('Finding mark file ', MarkName);
  253.         FoundIt := ExistFile(MarkName);
  254.       end;
  255.       if not FoundIt then
  256.         {Net marks are protected marks; stop checking if non-match found}
  257.         B := 0;
  258.       MatchFilMark := FoundIt;
  259.     end;
  260.  
  261.     function MatchExactFilMark(Segment : Word;
  262.                                MarkName : String;
  263.                                var B : BlockType) : Boolean;
  264.       {-Return true if FilMark matches current name}
  265.     var
  266.       FoundIt : Boolean;
  267.     begin
  268.       {Check the mark name stored in the PSP of the mark block}
  269.       FoundIt := (GetMarkName(Segment) = MarkName);
  270.       if FoundIt then begin
  271.         {Assure named file exists}
  272.         if Verbose then
  273.           WriteLn('Finding mark file ', MarkName);
  274.         FoundIt := ExistFile(MarkName);
  275.       end;
  276.       if not FoundIt then
  277.         dec(B);
  278.       MatchExactFilMark := FoundIt;
  279.     end;
  280.  
  281.   begin
  282.     B := BlockMax;
  283.     MemMark := False;
  284.     FilMark := False;
  285.     if UseHiMem then begin
  286.       {Scan for an exact match to the specified net mark}
  287.       repeat
  288.         BPsp := Blocks[B].Psp;
  289.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  290.           {Don't match any non-program block or this program}
  291.           Dec(B)
  292.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  293.           {A net mark}
  294.           FilMark := MatchExactFilMark(BPsp, MarkName, B)
  295.         else
  296.           {Not a net mark}
  297.           Dec(B);
  298.       until (B < 1) or FilMark;
  299.  
  300.     end else begin
  301.       {Scan from the last block down to find the last MARK TSR}
  302.       repeat
  303.         BPsp := Blocks[B].Psp;
  304.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  305.           {Don't match any non-program block or this program}
  306.           Dec(B)
  307.         else if HasIDstring(BPsp, MarkID, MarkOffset) then
  308.           {An in-memory mark}
  309.           MemMark := MatchMemMark(BPsp, MarkName, B)
  310.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  311.           {A net mark}
  312.           FilMark := MatchFilMark(BPsp, MarkName, B)
  313.         else
  314.           {Ignore normal file marks}
  315.           {Not a mark}
  316.           Dec(B);
  317.       until (B < 1) or MemMark or FilMark;
  318.     end;
  319.     FindMark := MemMark or FilMark;
  320.   end;
  321.  
  322.   procedure CheckReadError;
  323.     {-Check previous I/O operation}
  324.   begin
  325.     if IoResult = 0 then
  326.       Exit;
  327.     Abort('Error reading '+MarkName);
  328.   end;
  329.  
  330.   function PhysicalAddress(P : Pointer) : LongInt;
  331.   begin
  332.     PhysicalAddress := LongInt(OS(P).S) shl 4+OS(P).O;
  333.   end;
  334.  
  335.   procedure ValidateMarkFile;
  336.     {-Open mark file and assure it's valid}
  337.   type
  338.     IDArray = array[1..4] of Char;
  339.   var
  340.     ID : IDArray;
  341.     ExpectedID : IDArray;
  342.   begin
  343.     Assign(MarkF, MarkName);
  344.     Reset(MarkF, 1);
  345.     if IoResult <> 0 then
  346.       Abort('Mark file '+MarkName+' not found');
  347.     MarkFOpen := True;
  348.  
  349.     {Check the ID at the start of the file}
  350.     ExpectedID := NetMarkID;
  351.     BlockRead(MarkF, ID, SizeOf(IDArray));
  352.     CheckReadError;
  353.     if ID <> ExpectedID then
  354.       Abort(MarkName+' is not a valid net mark file');
  355.  
  356.     {Read the NUL device address}
  357.     BlockRead(MarkF, TestPtr, SizeOf(Pointer));
  358.     CheckReadError;
  359.     if PhysicalAddress(TestPtr) <> PhysicalAddress(DevicePtr) then begin
  360.       if Verbose then
  361.         WriteLn('Old NUL addr:', HexPtr(TestPtr),
  362.                 '   Current NUL addr:', HexPtr(DevicePtr));
  363.       Abort('Unexpected error. NUL device moved');
  364.     end;
  365.   end;
  366.  
  367.   procedure BufferFileTable;
  368.     {-Read the file table from the mark file into memory}
  369.   type
  370.     SftRecStub =
  371.       record
  372.         Next : SftRecPtr;
  373.         Count : Word;
  374.       end;
  375.   var
  376.     I : Word;
  377.     Size : Word;
  378.     P : Pointer;
  379.     S : SftRecStub;
  380.   begin
  381.     BlockRead(MarkF, FileTableCnt, SizeOf(Word));
  382.     for I := 1 to FileTableCnt do begin
  383.       BlockRead(MarkF, S, SizeOf(SftRecStub));
  384.       Size := 6+S.Count*FileRecSize;
  385.       GetMem(FileTableA[I], Size);
  386.       P := FileTableA[I];
  387.       Move(S, P^, SizeOf(SftRecStub));
  388.       Inc(OS(P).O, SizeOf(SftRecStub));
  389.       BlockRead(MarkF, P^, Size-SizeOf(SftRecStub));
  390.     end;
  391.     CheckReadError;
  392.   end;
  393.  
  394.   procedure ReadReg(var B : Byte);
  395.     {-Read a communications register from the mark file}
  396.   begin
  397.     BlockRead(MarkF, B, SizeOf(Byte));
  398.     CheckReadError;
  399.   end;
  400.  
  401.   procedure ReadMarkFile;
  402.     {-Read the mark file info into memory}
  403.   var
  404.     DevPtr : DeviceHeaderPtr;
  405.     Com : Byte;
  406.   begin
  407.     {Read the vector table from the mark file, into a temporary memory area}
  408.     BlockRead(MarkF, Vectors, 1024);
  409.     CheckReadError;
  410.  
  411.     {Read the BIOS miscellaneous save areas into temporary tables}
  412.     BlockRead(MarkF, EGAsavTable, 8);
  413.     BlockRead(MarkF, IntComTable, 16);
  414.     BlockRead(MarkF, ParentSeg, 2);
  415.     BlockRead(MarkF, ParentLen, 2);
  416.     BlockRead(MarkF, BiosLowTable, 18);
  417.     CheckReadError;
  418.  
  419.     {Read the stored EMS handles, if any}
  420.     BlockRead(MarkF, MarkEHandles, SizeOf(Word));
  421.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  422.     BlockRead(MarkF, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  423.     CheckReadError;
  424.  
  425.     {Read the stored XMS info, if any}
  426.     BlockRead(MarkF, MarkXHandles, SizeOf(Word));
  427.     GetMem(MarkXmsHandles, SizeOf(XmsHandleRecord)*MarkXHandles);
  428.     BlockRead(MarkF, MarkXmsHandles^, SizeOf(XmsHandleRecord)*MarkXHandles);
  429.     BlockRead(MarkF, MarkHmaStatus, SizeOf(Byte));
  430.     CheckReadError;
  431.  
  432.     {Read the device driver chain}
  433.     DevPtr := DevicePtr;
  434.     DevCnt := 0;
  435.     while OS(DevPtr).O <> $FFFF do begin
  436.       Inc(DevCnt);
  437.       GetMem(DevA[DevCnt], SizeOf(DeviceHeader));
  438.       BlockRead(MarkF, DevA[DevCnt]^, SizeOf(DeviceHeader));
  439.       CheckReadError;
  440.       with DevA[DevCnt]^ do
  441.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  442.     end;
  443.  
  444.     {Read the DOS data area table}
  445.     BlockRead(MarkF, DosData, $200);
  446.     CheckReadError;
  447.  
  448.     {Read the DOS internal variables table}
  449.     BlockRead(MarkF, DosTableSize, SizeOf(Word));
  450.     if DosTableSize <> 0 then begin
  451.       GetMem(DosTable, DosTableSize);
  452.       BlockRead(MarkF, DosTable^, DosTableSize);
  453.     end;
  454.     CheckReadError;
  455.  
  456.     {Read the internal file table}
  457.     BufferFileTable;
  458.  
  459.     {Read in the copy of COMMAND.COM's PSP}
  460.     BlockRead(MarkF, CommandPsp, $100);
  461.     CheckReadError;
  462.  
  463.     {Read in the address used for COMMAND.COM patching by NetWare}
  464.     BlockRead(MarkF, PatchOfst, SizeOf(Word));
  465.     BlockRead(MarkF, PatchSegm, SizeOf(Word));
  466.     CheckReadError;
  467.  
  468.     {Read in the DOS master environment}
  469.     BlockRead(MarkF, EnvLen, SizeOf(Word));
  470.     GetMem(EnvPtr, EnvLen);
  471.     BlockRead(MarkF, EnvPtr^, EnvLen);
  472.     CheckReadError;
  473.  
  474.     {Read in the communications data area}
  475.     BlockRead(MarkF, PicMask, SizeOf(Byte));
  476.     CheckReadError;
  477.     for Com := 1 to 2 do
  478.       with ComData[Com] do begin
  479.         BlockRead(MarkF, Base, SizeOf(Word));
  480.         CheckReadError;
  481.         if Base <> 0 then begin
  482.           ReadReg(IERReg);
  483.           ReadReg(LCRReg);
  484.           ReadReg(MCRReg);
  485.           ReadReg(BRLReg);
  486.           ReadReg(BRHreg);
  487.         end;
  488.       end;
  489.  
  490.     {Read in the CD-ROM info}
  491.     BlockRead(MarkF, CDCnt, SizeOf(Word));
  492.     if CDCnt <> 0 then
  493.       BlockRead(MarkF, CDInfo, CDCnt*SizeOf(CDROMDeviceRec));
  494.     CheckReadError;
  495.  
  496.     {Read in the allocated Mcb chain}
  497.     BlockRead(MarkF, McbG.Count, SizeOf(Word));
  498.     BlockRead(MarkF, McbG.Mcbs, 2*SizeOf(Word)*McbG.Count);
  499.     CheckReadError;
  500.  
  501.     {Close and possibly erase mark file}
  502.     RemoveMarkFile;
  503.   end;
  504.  
  505.   procedure RestoreCommState;
  506.     {-Restore the communications chips to their previous state}
  507.   var
  508.     Com : Byte;
  509.   begin
  510.     for Com := 1 to 2 do
  511.       with ComData[Com] do
  512.         if Base <> 0 then begin
  513.           Port[Base+IER] := IERReg; {Interrupt enable register}
  514.           NullJump;
  515.           Port[Base+MCR] := MCRReg; {Modem control register}
  516.           NullJump;
  517.           Port[Base+LCR] := LCRReg or $80; {Enable baud rate divisor registers}
  518.           NullJump;
  519.           Port[Base+BRL] := BRLReg; {Baud rate low}
  520.           NullJump;
  521.           Port[Base+BRH] := BRHReg; {Baud rate high}
  522.           NullJump;
  523.           Port[Base+LCR] := LCRReg; {Line control register}
  524.           NullJump;
  525.         end;
  526.     {Restore the interrupt mask}
  527.     Port[$21] := PicMask;
  528.   end;
  529.  
  530.   procedure CopyVectors;
  531.     {-Put interrupt vectors back into table}
  532.  
  533.     procedure Reset8259;
  534.       {-Reset the 8259 interrupt controller to its powerup state}
  535.       {-Interrupts assumed OFF prior to calling this routine}
  536.  
  537.       function ATmachine : Boolean;
  538.         {-Return true if machine is AT class}
  539.       var
  540.         MachType : Byte absolute $FFFF : $000E;
  541.       begin
  542.         case MachType of
  543.           $F8, $FC : ATmachine := True;
  544.         else
  545.           ATmachine := False;
  546.         end;
  547.       end;
  548.  
  549.       procedure Reset8259PC;
  550.         {-Reset the 8259 on a PC class machine}
  551.       begin
  552.         inline(
  553.           $E4/$21/                { in      al,$21}
  554.           $88/$C4/                { mov     ah,al}
  555.           $B0/$13/                { mov     al,$13}
  556.           $E6/$20/                { out     $20,al}
  557.           $B0/$08/                { mov     al,8}
  558.           $E6/$21/                { out     $21,al}
  559.           $B0/$09/                { mov     al,9}
  560.           $E6/$21/                { out     $21,al}
  561.           $88/$E0/                { mov     al,ah}
  562.           $E6/$21                 { out     $21,al}
  563.           );
  564.       end;
  565.  
  566.       procedure Reset8259AT;
  567.         {-Reset the 8259 interrupt controllers on an AT machine}
  568.       begin
  569.         inline(
  570.           $32/$C0/                { xor       al,al }
  571.           $E6/$F1/                { out       0f1h,al         ; Switch off an 80287 if necessary}
  572.           {Set up master 8259 }
  573.           $E4/$21/                { in        al,21h          ; Get current interrupt mask }
  574.           $8A/$E0/                { mov       ah,al           ; save it }
  575.           $B0/$11/                { mov       al,11h }
  576.           $E6/$20/                { out       20h,al }
  577.           $EB/$00/                { jmp       short $+2 }
  578.           $B0/$08/                { mov       al,8            ; Set up main interrupt vector number}
  579.           $E6/$21/                { out       21h,al }
  580.           $EB/$00/                { jmp       short $+2 }
  581.           $B0/$04/                { mov       al,4 }
  582.           $E6/$21/                { out       21h,al }
  583.           $EB/$00/                { jmp       short $+2 }
  584.           $B0/$01/                { mov       al,1 }
  585.           $E6/$21/                { out       21h,al }
  586.           $EB/$00/                { jmp       short $+2 }
  587.           $8A/$C4/                { mov       al,ah }
  588.           $E6/$21/                { out       21h,al }
  589.           {Set up slave 8259 }
  590.           $E4/$A1/                { in        al,0a1h         ; Get current interrupt mask }
  591.           $8A/$E0/                { mov       ah,al           ; save it }
  592.           $B0/$11/                { mov       al,11h }
  593.           $E6/$A0/                { out       0a0h,al }
  594.           $EB/$00/                { jmp       short $+2 }
  595.           $B0/$70/                { mov       al,70h }
  596.           $E6/$A1/                { out       0a1h,al }
  597.           $B0/$02/                { mov       al,2 }
  598.           $EB/$00/                { jmp       short $+2 }
  599.           $E6/$A1/                { out       0a1h,al }
  600.           $EB/$00/                { jmp       short $+2 }
  601.           $B0/$01/                { mov       al,1 }
  602.           $E6/$A1/                { out       0a1h,al }
  603.           $EB/$00/                { jmp       short $+2 }
  604.           $8A/$C4/                { mov       al,ah           ; Reset previous interrupt state }
  605.           $E6/$A1                 { out       0a1h,al }
  606.           );
  607.       end;
  608.  
  609.     begin
  610.       if ATmachine then
  611.         Reset8259AT
  612.       else
  613.         Reset8259PC;
  614.     end;
  615.  
  616.   begin
  617.     {Interrupts off}
  618.     IntsOff;
  619.  
  620.     {Reset 8259 if requested}
  621.     if Revector8259 then
  622.       Reset8259;
  623.  
  624.     {Reset the communications state if requested}
  625.     if RestoreComm then
  626.       RestoreCommState;
  627.  
  628.     {Restore the main interrupt vector table}
  629.     Move(Vectors, Mem[0:0], 1024);
  630.  
  631.     {Interrupts on}
  632.     IntsOn;
  633.  
  634.     {Flag that we don't want system restoring vectors for us}
  635.     VectorsRestored := True;
  636.  
  637.     Move(EGAsavTable, Mem[$40:$A8], 8); {EGA table}
  638.     Move(IntComTable, Mem[$40:$F0], 16); {Interapplications communication area}
  639.     {$IFDEF Debug}
  640.     writeln('Parent address: ', HexW(ParentSeg), ' Length: ', ParentLen);
  641.     {$ENDIF}
  642.     if ValidPsp(HiMemSeg, ParentSeg, ParentLen) then begin
  643.       {Don't restore parent if it no longer exists (applies to QEMM LOADHI)}
  644.       MemW[PrefixSeg:$16] := ParentSeg;
  645.       if not UseHiMem then
  646.         {Programs loaded into high memory have strange termination addresses}
  647.         Move(Mem[0:4*$22], Mem[PrefixSeg:$0A], 4); {Int 22 addresses}
  648.     end;
  649.     Move(BiosLowTable, Mem[$40:$0], 18); {BIOS Com, Printer, Equip flag}
  650.     Move(Mem[0:4*$23], Mem[PrefixSeg:$0E], 8); {Int 23,24 addresses}
  651.   end;
  652.  
  653.   procedure MarkBlocks(markBlock : BlockType);
  654.     {-Mark those blocks to be released}
  655.   var
  656.     db : BlockType;
  657.  
  658.     procedure BatchWarning(B : BlockType);
  659.       {-Warn about the trapping effect of batch files}
  660.     var
  661.       T : BlockType;
  662.     begin
  663.       ReturnCode := 1;
  664.       {Accumulate number of bytes temporarily trapped}
  665.       for T := 1 to B do
  666.         if Blocks[T].ReleaseIt then
  667.           Inc(TrappedBytes, LongInt(MemW[Blocks[T].Mcb:3]) shl 4);
  668.     end;
  669.  
  670.     procedure MarkBlocksAbove;
  671.       {-Mark blocks above the mark}
  672.     var
  673.       b : BlockType;
  674.     begin
  675.       for b := 1 to BlockMax do
  676.         with Blocks[b] do
  677.           if (b >= markBlock) and (mcb+1 = psp) and (memw[psp:$16] = psp) then begin
  678.             {Don't release blocks owned by master COMMAND.COM}
  679.             releaseIt := False;
  680.             BatchWarning(b);
  681.           end else if KeepMark then
  682.             {Release all but RELEASE and the mark}
  683.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  684.           else
  685.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  686.     end;
  687.  
  688.     procedure MarkUnallocatedBlocks;
  689.       {-Mark blocks that weren't allocated at time of mark}
  690.     var
  691.       TopSeg : Word;
  692.       b : BlockType;
  693.       m : BlockType;
  694.       Found : Boolean;
  695.     begin
  696.       {Find last low memory mcb}
  697.       TopSeg := TopOfMemSeg-1;
  698.       m := 1;
  699.       Found := False;
  700.       while (not Found) and (m <= McbG.Count) do
  701.         if McbG.Mcbs[m].mcb >= TopSeg then
  702.           Found := True
  703.         else
  704.           inc(m);
  705.  
  706.       {Mark out all mcbs associated with psp of last low memory mcb}
  707.       TopSeg := McbG.Mcbs[m-1].psp;
  708.       if TopSeg <> markPsp then
  709.         for m := 1 to McbG.Count do
  710.           with McbG.Mcbs[m] do
  711.             if psp = TopSeg then
  712.               psp := 0;
  713.  
  714.       for b := 1 to BlockMax do
  715.         with Blocks[b] do begin
  716.           Found := False;
  717.           m := 1;
  718.           while (not Found) and (m <= McbG.Count) do begin
  719.             Found := (McbG.Mcbs[m].psp <> 0) and (McbG.Mcbs[m].mcb = mcb);
  720.             inc(m);
  721.           end;
  722.           if Found then
  723.             {was allocated at time of mark, keep it now unless a mark to be released}
  724.             releaseIt := not KeepMark and (psp = markPsp)
  725.           else if (mcb+1 = psp) and (memw[psp:$16] = psp)  then
  726.             {Don't release blocks owned by master COMMAND.COM}
  727.             releaseIt := False
  728.           else if (psp <= $400) or (psp >= $FFF0) then
  729.             {Don't release blocks owned by system or 386MAX}
  730.             releaseIt := False
  731.           else
  732.             {not allocated at time of mark}
  733.             releaseIt := (psp <> PrefixSeg);
  734.         end;
  735.     end;
  736.  
  737.   begin
  738.     if UseHiMem then
  739.       MarkUnallocatedBlocks
  740.     else
  741.       MarkBlocksAbove;
  742.  
  743.     {$IFDEF Debug}
  744.     for db := 1 to BlockMax do
  745.       with Blocks[db] do
  746.         if releaseIt then
  747.           WriteLn(db:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  748.     ReadLn;
  749.     {$ENDIF}
  750.   end;
  751.  
  752.   function ReleaseBlock(Segm : Word) : Word; assembler;
  753.     {-Use DOS services to release memory block}
  754.   asm
  755.     mov ah,$49
  756.     mov es,Segm
  757.     int $21
  758.     jc  @Done
  759.     xor ax,ax
  760. @Done:
  761.   end;
  762.  
  763.   procedure ReleaseMem;
  764.     {-Release DOS memory marked for release}
  765.   var
  766.     b : BlockType;
  767.   begin
  768.     if Verbose then begin
  769.       WriteLn('Releasing DOS memory');
  770.       {$IFDEF Debug}
  771.       ReadLn;
  772.       {$ENDIF}
  773.     end;
  774.     for b := BlockMax downto 1 do
  775.       with blocks[b] do
  776.         if releaseIt then begin
  777.           {$IFDEF Debug}
  778.           WriteLn('          ', hexw(mcb), ' ', hexw(psp));
  779.           {$ENDIF}
  780.           if ReleaseBlock(mcb+1) <> 0 then begin
  781.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  782.             Abort('Memory may be a mess... Please reboot');
  783.           end;
  784.         end;
  785.     if Verbose then begin
  786.       WriteLn('Merging free blocks in high memory');
  787.       {$IFDEF Debug}
  788.       ReadLn;
  789.       {$ENDIF}
  790.     end;
  791.     MergeHiMemBlocks(HiMemSeg);
  792.   end;
  793.  
  794.   procedure RestoreEMSmap;
  795.     {-Restore EMS to state at time of mark}
  796.   var
  797.     O, N, NHandle : Word;
  798.  
  799.     procedure EmsError;
  800.     begin
  801.       WriteLn('Program error or EMS device not responding');
  802.       Abort('EMS memory may be a mess... Please reboot');
  803.     end;
  804.  
  805.     procedure MapAndFree(Handle : Word);
  806.     var
  807.       Status : Byte;
  808.     begin
  809.       Status := FreeEms(NHandle);
  810.       if Status = $86 then
  811.         Status := RestorePageMap(NHandle);
  812.       if Status <> 0 then
  813.         EmsError;
  814.     end;
  815.  
  816.   begin
  817.     {Get the existing EMS page map}
  818.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  819.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  820.     if CurrEHandles > MaxHandles then
  821.       WriteLn('EMS handle count exceeds capacity of RELNET -- no action taken')
  822.     else if CurrEHandles <> 0 then begin
  823.       {See how many handles were active when MARK was installed}
  824.       if Verbose then begin
  825.         WriteLn('Releasing EMS memory allocated since MARK');
  826.         {$IFDEF Debug}
  827.         ReadLn;
  828.         {$ENDIF}
  829.       end;
  830.       {Compare the two maps and deallocate pages not in the stored map}
  831.       for N := 1 to CurrEHandles do begin
  832.         {Scan all current handles}
  833.         NHandle := CurrEmsHandles^[N].Handle;
  834.         if MarkEHandles > 0 then begin
  835.           {See if current handle matches one stored by MARK}
  836.           O := 1;
  837.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  838.             Inc(O);
  839.           {If not, deallocate the current handle}
  840.           if (O > MarkEHandles) then
  841.             MapAndFree(NHandle);
  842.         end else
  843.           {No handles stored by MARK, deallocate all current handles}
  844.           MapAndFree(NHandle);
  845.       end;
  846.     end;
  847.   end;
  848.  
  849.   procedure RestoreXmsmap;
  850.     {-Restore Xms to state at time of mark}
  851.   var
  852.     O, N, NHandle : Word;
  853.  
  854.     procedure XmsError;
  855.     begin
  856.       WriteLn('Program error or XMS device not responding');
  857.       Abort('XMS memory may be a mess... Please reboot');
  858.     end;
  859.  
  860.   begin
  861.     CurrXHandles := GetXmsHandles(CurrXmsHandles);
  862.     if CurrXHandles <> 0 then begin
  863.       {See how many handles were active when MARK was installed}
  864.       if Verbose then begin
  865.         WriteLn('Releasing XMS memory allocated since MARK');
  866.         {$IFDEF Debug}
  867.         ReadLn;
  868.         {$ENDIF}
  869.       end;
  870.       if MarkXHandles = 0 then begin
  871.         {Release all current XMS Handles}
  872.         for N := 1 to CurrXHandles do
  873.           if FreeExtMem(CurrXmsHandles^[N].Handle) <> 0 then
  874.             XmsError;
  875.       end else begin
  876.         {Compare the two maps and deallocate pages not in the stored map}
  877.         for N := 1 to CurrXHandles do begin
  878.           {Scan all current handles}
  879.           NHandle := CurrXmsHandles^[N].Handle;
  880.           {See if current handle matches one stored by MARK}
  881.           O := 1;
  882.           while (MarkXmsHandles^[O].Handle <> NHandle) and (O <= MarkXHandles) do
  883.             Inc(O);
  884.           {If not, deallocate the current handle}
  885.           if (O > MarkXHandles) then
  886.             if FreeExtMem(NHandle) <> 0 then
  887.               XmsError;
  888.         end;
  889.       end;
  890.     end;
  891.  
  892.     {Free the HMA if appropriate}
  893.     CurHmaStatus := AllocateHma($FFFF);
  894.     if (CurHMAStatus = 0) or (MarkHMAStatus = 0) then
  895.       if FreeHma = 0 then ;
  896.   end;
  897.  
  898.   procedure GetOptions;
  899.     {-Analyze command line for options}
  900.  
  901.     procedure WriteCopyright;
  902.     begin
  903.       WriteLn('RELNET ', Version, ', Copyright 1993 TurboPower Software');
  904.     end;
  905.  
  906.     procedure WriteHelp;
  907.       {-Show the options}
  908.     begin
  909.       WriteCopyright;
  910.       WriteLn;
  911.       WriteLn('RELNET removes memory-resident programs from memory, particularly network');
  912.       WriteLn('shells like Novell''s NetWare, although it will also release normal memory');
  913.       WriteLn('resident programs. In combination with MARKNET it thoroughly restores the');
  914.       WriteLn('system to its state at the time MARKNET was called.');
  915.       WriteLn;
  916.       WriteLn('RELNET accepts the following command line syntax:');
  917.       WriteLn;
  918.       WriteLn('  RELNET NetMarkFile [Options]');
  919.       WriteLn;
  920.       WriteLn('Options may be preceded by either / or -. Valid options are:');
  921.       WriteLn;
  922.       WriteLn('  /C         do NOT restore communications state.');
  923.       WriteLn('  /E         do NOT access EMS memory.');
  924.       WriteLn('  /H         work with upper memory if available.');
  925.       WriteLn('  /I         do NOT shut down IPX events and sockets.');
  926.       WriteLn('  /K         release memory, but keep the mark in place.');
  927.       WriteLn('  /L         do NOT restore CD-ROM drive letters.');
  928.       WriteLn('  /P         do NOT restore DOS environment.');
  929.       WriteLn('  /Q         write no screen output.');
  930.       WriteLn('  /R         revector 8259 interrupt controller to powerup state.');
  931.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  932.       WriteLn('  /T         do NOT reset system timer chip to default rate.');
  933.       WriteLn('  /U         work with upper memory, but halt if none found.');
  934.       WriteLn('  /V         verbose: show each step of the restore.');
  935.       WriteLn('  /X         do NOT access XMS memory.');
  936.       WriteLn('  /?         write this help screen.');
  937.       Halt(1);
  938.     end;
  939.  
  940.     procedure GetArgs(S : String);
  941.     var
  942.       SPos : Word;
  943.       Arg : String[127];
  944.     begin
  945.       SPos := 1;
  946.       repeat
  947.         Arg := NextArg(S, SPos);
  948.         if Arg = '' then
  949.           Exit;
  950.         if Arg[1] = '?' then
  951.           WriteHelp
  952.         else if (Arg[1] = '-') or (Arg[1] = '/') then
  953.           case Length(Arg) of
  954.             1 : Abort('Missing command option following '+Arg);
  955.             2 : case Upcase(Arg[2]) of
  956.                   'C' : RestoreComm := False;
  957.                   'E' : DealWithEMS := False;
  958.                   'H' : OptUseHiMem := True;
  959.                   'I' : DealWithIPX := False;
  960.                   'K' : KeepMark := True;
  961.                   'L' : DealWithCDs := False;
  962.                   'P' : RestoreEnvir := False;
  963.                   'Q' : Quiet := True;
  964.                   'R' : Revector8259 := True;
  965.                   'S' : begin
  966.                           Arg := NextArg(S, SPos);
  967.                           if Length(Arg) = 0 then
  968.                             Abort('Key string missing');
  969.                           if Length(Arg) > 15 then
  970.                             Abort('No more than 15 keys may be stuffed');
  971.                           Keys := Arg+^M;
  972.                         end;
  973.                   'T' : ResetTimer := False;
  974.                   'U' : UseHiMem := True;
  975.                   'V' : Verbose := True;
  976.                   'X' : DealWithXMS := False;
  977.                   '?' : WriteHelp;
  978.                 else
  979.                   Abort('Unknown command option: '+Arg);
  980.                 end;
  981.           else
  982.             Abort('Unknown command option: '+Arg);
  983.           end
  984.         else if Length(MarkName) = 0 then
  985.           {Mark file}
  986.           MarkName := StUpcase(Arg)
  987.         else
  988.           Abort('Too many mark files specified');
  989.       until False;
  990.     end;
  991.  
  992.   begin
  993.     {Initialize defaults}
  994.     MarkName := '';
  995.     Keys := '';
  996.  
  997.     Revector8259 := False;
  998.     KeepMark := False;
  999.     DealWithIPX := True;
  1000.     DealWithEMS := True;
  1001.     DealWithXMS := True;
  1002.     ResetTimer := True;
  1003.     Verbose := False;
  1004.     Quiet := False;
  1005.     RestoreEnvir := True;
  1006.     RestoreComm := True;
  1007.     UseHiMem := False;
  1008.     OptUseHiMem := False;
  1009.     DealWithCDs := True;
  1010.  
  1011.     ReturnCode := 0;
  1012.     TrappedBytes := 00;
  1013.  
  1014.     {Get arguments from the command line and the environment}
  1015.     GetArgs(StringPtr(Ptr(PrefixSeg, $80))^);
  1016.     GetArgs(GetEnv('RELNET'));
  1017.  
  1018.     if Length(MarkName) = 0 then begin
  1019.       WriteLn('No mark file specified');
  1020.       WriteHelp;
  1021.     end;
  1022.     if Verbose then
  1023.       Quiet := False;
  1024.     if not Quiet then
  1025.       WriteCopyright;
  1026.  
  1027.     {Initialize for high memory access}
  1028.     if OptUseHiMem or UseHiMem then begin
  1029.       HiMemSeg := FindHiMemStart;
  1030.       if HiMemSeg = 0 then begin
  1031.         if UseHiMem then
  1032.           Abort('No upper memory blocks found');
  1033.       end else
  1034.         UseHiMem := True;
  1035.     end else
  1036.       HiMemSeg := 0;
  1037.   end;
  1038.  
  1039.   function MemoryRelease(P : Pointer) : Boolean;
  1040.     {-Return True if address P is in a block to be released}
  1041.   var
  1042.     B : BlockType;
  1043.     PL : LongInt;
  1044.     PSPL : LongInt;
  1045.   begin
  1046.     PL := PhysicalAddress(P);
  1047.     for B := 1 to BlockMax do
  1048.       with Blocks[B] do
  1049.         if ReleaseIt then begin
  1050.           PSPL := LongInt(Psp) shl 4;
  1051.           if (PL >= PSPL) and (PL < PSPL+LongInt(MemW[Mcb:3]) shl 4) then begin
  1052.             MemoryRelease := True;
  1053.             Exit;
  1054.           end;
  1055.         end;
  1056.     MemoryRelease := False;
  1057.   end;
  1058.  
  1059.   procedure CloseIpxSockets;
  1060.   const
  1061.     Retf : Byte = $CB; {Return instruction}
  1062.   var
  1063.     This, Next : IpxEcbPtr;
  1064.     Ecb : IpxEcb;
  1065.     Status : Byte;
  1066.   begin
  1067.     {Create a new Ecb to find start of linked list of Ecb's}
  1068.     FillChar(Ecb, SizeOf(IpxEcb), 0);
  1069.     Ecb.EsrAddress := @RetF;
  1070.     ScheduleSpecialEvent(182, Ecb);
  1071.  
  1072.     {Scan the list of Ecb's}
  1073.     This := Ecb.Link;
  1074.     while This <> nil do begin
  1075.       if Verbose then
  1076.         Write('Ecb: ', HexPtr(This),
  1077.               ' Esr: ', HexPtr(This^.EsrAddress),
  1078.               ' InUse: ', HexW(This^.InUse),
  1079.               ' Socket: ', HexW(This^.SocketNumber));
  1080.       Next := This^.Link;
  1081.       if MemoryRelease(This) or MemoryRelease(This^.ESRAddress) then
  1082.         {Memory of this Ecb will be released}
  1083.         if This^.InUse <> 0 then begin
  1084.           {This Ecb is in use}
  1085.           Status := CancelEvent(This^);
  1086.           if Verbose then
  1087.             Write(' [cancelled]');
  1088.           if This^.SocketNumber <> 0 then begin
  1089.             CloseSocket(This^.SocketNumber);
  1090.             if Verbose then
  1091.               Write(' [closed]');
  1092.           end;
  1093.         end;
  1094.       if Verbose then
  1095.         Writeln;
  1096.       This := Next;
  1097.     end;
  1098.  
  1099.     {Cancel the special event we started}
  1100.     Status := CancelEvent(Ecb);
  1101.   end;
  1102.  
  1103.   procedure FindDevChain;
  1104.     {-Return segment, offset and pointer to NUL device}
  1105.   begin
  1106.     DosPtr := Ptr(OS(DosList).S, OS(DosList).O-2);
  1107.     DevicePtr := @DosPtr^.NullDevice;
  1108.     DeviceSegment := OS(DevicePtr).S;
  1109.     DeviceOffset := OS(DevicePtr).O;
  1110.   end;
  1111.  
  1112.   procedure RestoreDosTable;
  1113.     {-Restore the DOS variables table, except for the buffer pointer}
  1114.   type
  1115.     ByteArray = array[0..32767] of Byte;
  1116.     ByteArrayPtr = ^ByteArray;
  1117.   var
  1118.     DosBase : Pointer;
  1119.     SPtr : Pointer;
  1120.     DPtr : Pointer;
  1121.   begin
  1122.     if Verbose then begin
  1123.       WriteLn('Restoring DOS data area at 0050:0000');
  1124.       {$IFDEF Debug}
  1125.       ReadLn;
  1126.       {$ENDIF}
  1127.     end;
  1128.     DPtr := Ptr($50, 0);
  1129.     Move(DosData, DPtr^, $200);
  1130.  
  1131.     DosBase := Ptr(OS(DosPtr).S, 0);
  1132.     if Verbose then begin
  1133.       WriteLn('Restoring ', DosTableSize,
  1134.               ' bytes of DOS variables table at ', HexPtr(DosBase));
  1135.       {$IFDEF Debug}
  1136.       ReadLn;
  1137.       {$ENDIF}
  1138.     end;
  1139.  
  1140.     {patch up DosTable to reflect current items that must be maintained}
  1141.     {CachePtr}
  1142.     SPtr := @DosPtr^.CachePtr;
  1143.     DPtr := @ByteArrayPtr(DosTable)^[Ofs(DosPtr^.CachePtr)];
  1144.     {$IFDEF Debug}
  1145.     writeln('cacheptr ', hexptr(sptr), '->', hexptr(dptr), ' ', SizeOf(Pointer));
  1146.     {$ENDIF}
  1147.  
  1148.     move(SPtr^, DPtr^, SizeOf(Pointer));
  1149.     if DosV = 5 then begin
  1150.       {Other unknown areas}
  1151.       SPtr := Ptr(OS(DosPtr).S, OS(DosPtr).O+SizeOf(DosRec));
  1152.       DPtr := @ByteArrayPtr(DosTable)^[OS(DosPtr).O+SizeOf(DosRec)];
  1153.       {$IFDEF Debug}
  1154.       writeln('unknown  ', hexptr(sptr), '->', hexptr(dptr), ' ',
  1155.               OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1156.       {$ENDIF}
  1157.       move(SPtr^, DPtr^, OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1158.     end;
  1159.  
  1160.     {Restore DOS table}
  1161.     move(DosTable^, DosBase^, DosTableSize);
  1162.   end;
  1163.  
  1164.   procedure RestoreFileTable;
  1165.     {-Copy the internal file table from our memory buffer to its DOS location}
  1166.   var
  1167.     S : SftRecPtr;
  1168.     I : Word;
  1169.   begin
  1170.     S := DosPtr^.FirstSFT;
  1171.     if Verbose then begin
  1172.       WriteLn('Restoring DOS file table at ', HexPtr(S));
  1173.       {$IFDEF Debug}
  1174.       ReadLn;
  1175.       {$ENDIF}
  1176.     end;
  1177.     for I := 1 to FileTableCnt do begin
  1178.       Move(FileTableA[I]^, S^, 6+FileTableA[I]^.Count*FileRecSize);
  1179.       S := S^.Next;
  1180.     end;
  1181.   end;
  1182.  
  1183.   procedure RestoreDeviceDrivers;
  1184.     {-Restore the device driver chain to its original state}
  1185.   var
  1186.     D : Word;
  1187.     DevPtr : DeviceHeaderPtr;
  1188.   begin
  1189.     if Verbose then begin
  1190.       WriteLn('Restoring device driver chain');
  1191.       {$IFDEF Debug}
  1192.       ReadLn;
  1193.       {$ENDIF}
  1194.     end;
  1195.     DevPtr := DevicePtr;
  1196.     for D := 1 to DevCnt do begin
  1197.       DevPtr^ := DevA[D]^;
  1198.       with DevA[D]^ do
  1199.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  1200.     end;
  1201.   end;
  1202.  
  1203.   procedure RestoreCommandPSP;
  1204.     {-Copy COMMAND.COM's PSP back into place}
  1205.   var
  1206.     PspPtr : Pointer;
  1207.   begin
  1208.     PspPtr := Ptr(CommandSeg, 0);
  1209.     if Verbose then begin
  1210.       WriteLn('Restoring COMMAND.COM PSP at ', HexPtr(PspPtr));
  1211.       {$IFDEF Debug}
  1212.       ReadLn;
  1213.       {$ENDIF}
  1214.     end;
  1215.     Move(CommandPsp, PspPtr^, $100);
  1216.   end;
  1217.  
  1218.   procedure RestoreCommandPatch;
  1219.     {-Restore the patch that NetWare applies to COMMAND.COM}
  1220.   begin
  1221.     if (PatchSegm <> 0) or (PatchOfst <> 0) then
  1222.       if (Mem[PatchSegm:PatchOfst+$01] <> Byte('/')) or
  1223.       (Mem[PatchSegm:PatchOfst+$11] <> Byte('/')) then begin
  1224.         if Verbose then begin
  1225.           WriteLn('Removing patch at ', HexW(PatchSegm), ':', HexW(PatchOfst));
  1226.           {$IFDEF Debug}
  1227.           ReadLn;
  1228.           {$ENDIF}
  1229.         end;
  1230.         Mem[PatchSegm:PatchOfst+$01] := Byte('/');
  1231.         Mem[PatchSegm:PatchOfst+$11] := Byte('/');
  1232.       end;
  1233.   end;
  1234.  
  1235.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  1236.     {-Return the segment and length of the master environment}
  1237.   var
  1238.     Mcb : Word;
  1239.   begin
  1240.     Mcb := CommandSeg-1;
  1241.     EnvSeg := MemW[CommandSeg:$2C];
  1242.     if EnvSeg = 0 then
  1243.       {Master environment is next block past COMMAND}
  1244.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  1245.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  1246.   end;
  1247.  
  1248.   procedure RestoreDosEnvironment;
  1249.     {-Restore the master copy of the DOS environment}
  1250.   var
  1251.     EnvSeg : Word;
  1252.     CurLen : Word;
  1253.     P : Pointer;
  1254.   begin
  1255.     if RestoreEnvir then begin
  1256.       FindEnv(CommandSeg, EnvSeg, CurLen);
  1257.       if CurLen <> EnvLen then
  1258.         Abort('Environment length changed');
  1259.       if Verbose then begin
  1260.         WriteLn('Restoring DOS environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  1261.         {$IFDEF Debug}
  1262.         ReadLn;
  1263.         {$ENDIF}
  1264.       end;
  1265.       P := Ptr(EnvSeg, 0);
  1266.       move(EnvPtr^, P^, EnvLen);
  1267.     end;
  1268.   end;
  1269.  
  1270.   procedure SetTimerRate(Rate : Word);
  1271.     {-Program system 8253 timer number 0 to run at specified rate}
  1272.   begin
  1273.     IntsOff;
  1274.     Port[$43] := $36;
  1275.     NullJump;
  1276.     Port[$40] := Lo(Rate);
  1277.     NullJump;
  1278.     Port[$40] := Hi(Rate);
  1279.     IntsOn;
  1280.   end;
  1281.  
  1282.   procedure RestoreTimer;
  1283.     {-Set the system timer to its normal rate}
  1284.   begin
  1285.     if Verbose then begin
  1286.       WriteLn('Restoring system timer to normal rate');
  1287.       {$IFDEF Debug}
  1288.       ReadLn;
  1289.       {$ENDIF}
  1290.     end;
  1291.     SetTimerRate(0);
  1292.   end;
  1293.  
  1294.   procedure RestoreCDROMs;
  1295.     {-Restore drive letters used by MSCDEX}
  1296.   var
  1297.     CurCDCnt : Word;
  1298.     I : Word;
  1299.     J : Word;
  1300.     CDP : CurDirRecPtr;
  1301.     Found : Boolean;
  1302.     DLet : Char;
  1303.     CurCDInfo : CDROMDeviceArray;
  1304.   begin
  1305.     if not DealWithCDs then
  1306.       exit;
  1307.     if Verbose then begin
  1308.       Write('Restoring CD-ROM device letters');
  1309.       {$IFDEF Debug}
  1310.       ReadLn;
  1311.       {$ENDIF}
  1312.     end;
  1313.     CurCDCnt := GetCDCount(CurCDInfo);
  1314.     if CurCDCnt > CDCnt then
  1315.       {MSCDEX is being unloaded}
  1316.       for I := 1 to CurCDCnt do begin
  1317.         {Is current CD in the original CD list?}
  1318.         Found := False;
  1319.         J := 1;
  1320.         while not(Found) and (J <= CDCnt) do
  1321.           if (CurCDInfo[I].SubUnit = CDInfo[J].SubUnit) and
  1322.              (CurCDInfo[I].Header = CDInfo[J].Header) and
  1323.              (CurCDInfo[I].Header^.DriveLet = CDInfo[J].Header^.DriveLet) then
  1324.             Found := True
  1325.           else
  1326.             inc(J);
  1327.         if not(Found) then begin
  1328.           DLet := Char(Byte('A')+CurCDInfo[I].Header^.DriveLet-1);
  1329.           if DLet >= 'A' then begin
  1330.             if Verbose then
  1331.               Write(' ', DLet);
  1332.  
  1333.             {Clear DOS CurDir record for this drive}
  1334.             CDP := DosPtr^.CurDirTable;
  1335.             inc(LongInt(CDP), (Byte(DLet)-Byte('A'))*CurDirRecSize);
  1336.             with CDP^ do begin
  1337.               {Restore default path and installable file system info}
  1338.               DrivePath[0] := DLet;
  1339.               DrivePath[1] := ':';
  1340.               DrivePath[2] := '\';
  1341.               DrivePath[3] := #0;
  1342.               Flags := 0;
  1343.               DPB := nil;
  1344.               RedirIfs := Ptr($FFFF, $FFFF);
  1345.               Param := $FFFF;
  1346.               BackSlashOfs := 2;
  1347.             end;
  1348.  
  1349.             {Clear drive letter for this header}
  1350.             CurCDInfo[I].Header^.DriveLet := 0;
  1351.           end;
  1352.         end;
  1353.       end;
  1354.     if Verbose then
  1355.       WriteLn;
  1356.   end;
  1357.  
  1358.   function CompaqDOS30 : Boolean; assembler;
  1359.     {-Return true if Compaq DOS 3.0}
  1360.   asm
  1361.     mov ah,$34
  1362.     int $21
  1363.     cmp bx,$019C
  1364.     mov al,1
  1365.     jz @Done
  1366.     dec al
  1367. @Done:
  1368.   end;
  1369.  
  1370.   procedure ValidateDosVersion;
  1371.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  1372.   var
  1373.     DosVer : Word;
  1374.   begin
  1375.     DosVer := DosVersion;
  1376.     CurDirRecSize := 81;
  1377.     case Hi(DosVer) of
  1378.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  1379.             {IBM DOS 3.0}
  1380.             FileRecSize := 56
  1381.           else
  1382.             {DOS 3.1+ or Compaq DOS 3.0}
  1383.             FileRecSize := 53;
  1384.       4, 5, 6 :
  1385.         begin
  1386.           FileRecSize := 59;
  1387.           CurDirRecSize := 88;
  1388.         end;
  1389.     else
  1390.       Abort('Requires DOS 3 - 6');
  1391.     end;
  1392.   end;
  1393.  
  1394. begin
  1395.   {Assure supported version of DOS}
  1396.   ValidateDosVersion;
  1397.  
  1398.   {Analyze command line for options}
  1399.   GetOptions;
  1400.  
  1401.   {Find the start of the device driver chain via the NUL device}
  1402.   FindDevChain;
  1403.  
  1404.   {Get all allocated memory blocks in normal memory}
  1405.   FindTheBlocks(True, HiMemSeg, Blocks, BlockMax, StartMcb);
  1406.   CommandSeg := MasterCommandSeg(HiMemSeg);
  1407.  
  1408.   {Find the block marked with the MARK idstring, and MarkName if specified}
  1409.   if not(FindMark(MarkName, MarkID, MarkOffset, MemMark, FilMark, markBlock)) then
  1410.     Abort('No matching marker found, or protected marker encountered.');
  1411.   if MemMark then
  1412.     Abort('Marker must have been placed by MARKNET');
  1413.   markPsp := Blocks[markBlock].psp;
  1414.  
  1415.   {Open and validate the mark file}
  1416.   ValidateMarkFile;
  1417.  
  1418.   {Close IPX sockets and cancel IPX ECBs}
  1419.   if DealWithIpx then
  1420.     if IpxInstalled then
  1421.       CloseIpxSockets;
  1422.  
  1423.   {Get file mark information into memory}
  1424.   ReadMarkFile;
  1425.  
  1426.   {Restore the CD-ROM drive letters}
  1427.   RestoreCDROMs;
  1428.  
  1429.   {Mark those blocks to be released}
  1430.   MarkBlocks(markBlock);
  1431.  
  1432.   {Copy the vector table from the MARK copy}
  1433.   CopyVectors;
  1434.  
  1435.   {Restore the device driver chain}
  1436.   RestoreDeviceDrivers;
  1437.  
  1438.   {Restore the COMMAND.COM patch possibly made by NetWare}
  1439.   RestoreCommandPatch;
  1440.  
  1441.   {Restore the DOS variables table}
  1442.   RestoreDosTable;
  1443.  
  1444.   {Restore the DOS file table}
  1445.   RestoreFileTable;
  1446.  
  1447.   {Restore the COMMAND.COM PSP}
  1448.   RestoreCommandPSP;
  1449.  
  1450.   {Restore the master DOS environment}
  1451.   RestoreDosEnvironment;
  1452.  
  1453.   {Set the timer to normal rate}
  1454.   if ResetTimer then
  1455.     RestoreTimer;
  1456.  
  1457. (*
  1458.   this isn't necessary, and in fact is harmful, when the DOS file table
  1459.   is being restored above.
  1460.   {Close open file handles}
  1461.   CloseHandles;
  1462. *)
  1463.  
  1464.   {Release normal memory}
  1465.   ReleaseMem;
  1466.  
  1467.   {Deal with expanded memory}
  1468.   if DealWithEMS then
  1469.     if EMSpresent then
  1470.       RestoreEMSmap;
  1471.  
  1472.   {Deal with extended memory}
  1473.   if DealWithXMS then
  1474.     if XMSInstalled then
  1475.       RestoreXMSMap;
  1476.  
  1477.   {Write success message}
  1478.   if not Quiet then
  1479.     WriteLn('Memory released after ', StUpcase(MarkName));
  1480.  
  1481.   if (ReturnCode <> 0) and Verbose then
  1482.     WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  1483.  
  1484.   {Stuff keyboard buffer if requested}
  1485.   if Length(Keys) > 0 then
  1486.     StuffKeys(Keys, True);
  1487.  
  1488.   NoRestoreHalt(ReturnCode);
  1489. end.
  1490.